﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using PMatch;
using PMatch.Experimentation;
using PMatch.Formats.SequenceSample;
using PMatch.Formats.sPMatch;
using PMatch.Sampling;

namespace Exp1
{

	public class ExpUnit : ExperimentUnit
	{
		public int States { get; set; }
		public double Rtol { get; set; }
	}

	public struct WAndS
	{
		public WAndS(int index, SampleWindow wnd)
		{
			Index = index;
			Window = wnd;
		}
		public int Index;
		public SampleWindow Window;
	}

	public abstract class ExpProto
	{
		TextWriter Log;

		public void Run(string fout)
		{
			Log = Console.Out;
			var seqs = sequences.LoadFromFile("test.xml");
			Log.WriteLine("Secuencias cargadas");

			var windowsAndSitesArray = SetupWindowsAndSites();
			// carga la lista de elementos a procesar y aplana los resultados
			var windowsAndSites = from x in windowsAndSitesArray
														from y in x
														select new WAndS(Array.IndexOf(windowsAndSitesArray, x), y);

			// Muestrea cada ventana por cada sitio
			var samples = windowsAndSites.Select(x => new
				{
					WindowAndSite = x,
					Samples = SampleCollector.CollectSamples(seqs.sequence, x.Window, false, x.Index),
				});

			// Opciones de tolerancia y cantidades de estados
			var rtolArray = new double[] { 1e-5, 1e-12, 1e-30 };
			var stateArray = new int[] { 2, 8, 15 };

			// Construye los problemas unitarios
			var units = from rtol in rtolArray
									from state in stateArray
									from sample in samples
									let resolvedSamples = SampleCollector.ResolveCollisions(sample.Samples) // Resuelve colisiones
									let dataset = SampleCollector.CreateDataset(resolvedSamples) // Construimos dataset
									let datasets = dataset.SplitDataset(null, 0.8m) // Divide en Train: 80% / Test: 20%									
									select new ExpUnit()
									{
										Rtol = rtol,
										Site = sample.WindowAndSite.Index,
										Window = sample.WindowAndSite.Window,
										States = state,
										Train = datasets[0],
										Test = datasets[1],
									};

			var res = units.Select(x =>
			{
				x.Elapsed = ExperimentUtil.Time(() =>
				{
					var model = TrainModel(x);
					TestModel(model, x);
				});
				return x;
			});

			var doc = res.ToDocument();
			doc.Save(fout);
		}

		private void TestModel(IMarkovModel model, ExpUnit x)
		{
			var rs1 = x.Test.positive.Count(y => model.Evaluate(new sample(y)));
			var rs2 = x.Test.negative.Count(y => model.Evaluate(new sample(y)));
			x.SetStats(rs1, rs2);
			x.SetCounts();
			x.Train = null; // Esto para liberar memoria, eliminamos las referencias y dejamos
			x.Test = null; // que puedan ser garbage collected
		}

		private IMarkovModel TrainModel(ExpUnit x)
		{
			Log.WriteLine("Site:{0} {1} States:{2} Tolerance:{3}", x.Site, x.Window, x.States, x.Rtol);
			var model = GetModel(x);
			model.Train(x.Train);
			return model;
		}

		protected abstract IMarkovModel GetModel(ExpUnit x);

		private List<SampleWindow>[] SetupWindowsAndSites()
		{
			var sites = new List<SampleWindow>[9];
			sites[0] = new List<SampleWindow>(){
				new SampleWindow(1,5),
				new SampleWindow(6,0),
				new SampleWindow(0,15),
				new SampleWindow(15,0),
			};
			sites[1] = new List<SampleWindow>(){
				new SampleWindow(3,4),
				new SampleWindow(6,3),
				new SampleWindow(0,15),
				new SampleWindow(15,15),
			};
			sites[2] = new List<SampleWindow>(){
				new SampleWindow(4,2),
				new SampleWindow(2,5),
				new SampleWindow(0,15),
				new SampleWindow(15,0),
				new SampleWindow(15,15),
			};
			sites[3] = new List<SampleWindow>(){
				new SampleWindow(0,6),
				new SampleWindow(3,3),
				new SampleWindow(0,15),
				new SampleWindow(15,0),
				new SampleWindow(15,15),
			};
			sites[4] = new List<SampleWindow>(){
				new SampleWindow(0,7),				
				new SampleWindow(0,15),
				new SampleWindow(15,0),
				new SampleWindow(15,15),
			};
			sites[5] = new List<SampleWindow>(){
				new SampleWindow(3,4),
				new SampleWindow(4,3),
				new SampleWindow(2,6),
				new SampleWindow(0,15),
				new SampleWindow(15,0),
				new SampleWindow(15,15),
			};
			sites[6] = new List<SampleWindow>(){
				new SampleWindow(7,0),				
				new SampleWindow(0,15),
				new SampleWindow(15,0),
				new SampleWindow(15,15),
			};
			sites[7] = new List<SampleWindow>(){
				new SampleWindow(2,4),
				new SampleWindow(4,2),				
				new SampleWindow(0,15),
				new SampleWindow(15,0),
				new SampleWindow(15,15),
			};
			sites[8] = new List<SampleWindow>(){
				new SampleWindow(1,6),
				new SampleWindow(0,8),
				new SampleWindow(0,10),
				new SampleWindow(0,15),
				new SampleWindow(15,0),
				new SampleWindow(15,15),
			};
			return sites;
		}
	}
}
